package first.group.ssh.authorize;

import first.group.ssh.ConnectionCallback;
import first.group.ssh.PacketConnectStage;
import first.group.ssh.SSHConsts;
import first.group.ssh.authorize.Authorization.AuthorizationCallback;
import first.group.ssh.packet.Packet;
import first.group.ssh.packet.ServiceAcceptPacket;
import first.group.ssh.packet.ServiceRequestPacket;
import first.group.ssh.packet.UserAuthFailurePacket;
import first.group.ssh.packet.UserAuthSuccessPacket;
import java.io.IOException;
import java.util.List;

public class Authorizer extends PacketConnectStage implements AuthorizationCallback, AuthorizationLauncher {

    private final ConnectionCallback callback;

    public interface AuthorizeListener {

        void beginAuthorize(AuthorizationLauncher launcher);

        void authorizeFailed(AuthorizationLauncher launcher, List<String> continueMethods, boolean partialSuccess);

        void authorizeSucceed();
    }

    private AuthorizeListener listener = null;

    private enum Status {
        WAIT_SERVICE,
        WAIT_REQUEST,
        WAIT_AUTHORIZATION
    }

    private Status status = Status.WAIT_SERVICE;

    public Authorizer(ConnectionCallback callback) {
        this.callback = callback;
    }

    public void setAuthorizeListener(AuthorizeListener listener) {
        this.listener = listener;
    }

    private Authorization authorization = null;

    @Override
    public void init() {
        callback.appendPacket(new ServiceRequestPacket(SSHConsts.SERVICE_NAME_AUTH));
    }

    @Override
    public void beginAuthorization(String userName, Authorization authorization) {
        this.authorization = authorization;
        authorization.setConnectionCallback(callback);
        authorization.setAuthorizationCallback(this);
        authorization.setUsername(userName);
        authorization.init();
        status = Status.WAIT_AUTHORIZATION;
    }

    @Override
    public void authorizeFailed(List<String> continueMethods, boolean partialSuccess) {
        listener.authorizeFailed(this, continueMethods, partialSuccess);
        status = Status.WAIT_REQUEST;
    }

    @Override
    public void authorizeSucceed() {
        listener.authorizeSucceed();
        callback.finish();
    }

    @Override
    public boolean shouldRead() {
        switch (status) {
            case WAIT_SERVICE -> {
                return true;
            }
            case WAIT_REQUEST -> {
                return false;
            }
            case WAIT_AUTHORIZATION -> {
                return authorization.shouldRead();
            }
            default -> throw new AssertionError(status.name());
        }
    }

    @Override
    public void read(Packet packet) throws IOException {
        switch (status) {
            case WAIT_SERVICE -> {
                if (!(packet instanceof ServiceAcceptPacket)) {
                    throw new IllegalStateException("Want ServiceAcceptPacket, but get " + packet);
                }
                if (listener == null) {
                    throw new IllegalStateException("AuthorizeListener is required");
                }
                status = Status.WAIT_REQUEST;
                listener.beginAuthorize(this);
            }
            case WAIT_REQUEST -> {
                throw new IllegalStateException("Dont want to receive in status " + status.name());
            }
            case WAIT_AUTHORIZATION -> {
                if (packet instanceof UserAuthSuccessPacket) {
                    listener.authorizeSucceed();
                    callback.finish();
                } else if (packet instanceof UserAuthFailurePacket failurePacket) {
                    listener.authorizeFailed(this, failurePacket.continueMethods, failurePacket.partialSuccess);
                } else {
                    authorization.read(packet);
                }
            }
            default -> throw new AssertionError(status.name());
        }
    }
}
